package com.authentication;

import com.authentication.token.Oauth2Token;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;

import static org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType.BEARER;
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.*;

/**
 * 使用 authorization code 换取 token (access token 和 refresh token)
 * 再使用 access token 换取 用户信息 user info
 * 对 {@link OAuth2LoginAuthenticationProvider} 的简易实现
 */
public class SimpleOauth2LoginAuthenticationManager implements AuthenticationManager {


    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RestTemplate restTemplate = new RestTemplate();;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        if (authentication instanceof OAuth2LoginAuthenticationToken) {

            OAuth2LoginAuthenticationToken loginToken = (OAuth2LoginAuthenticationToken) authentication;

            ClientRegistration clientRegistration = loginToken.getClientRegistration();

            Oauth2Token oauth2Token = getOauth2Token(loginToken, clientRegistration);

            Map<String,Object> userInfo = getUserInfo(oauth2Token,clientRegistration);


            DefaultOAuth2User principle = new DefaultOAuth2User(
                    null,
                    userInfo,
                    clientRegistration.getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName()
            );




            Instant now = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant();
            Instant expiredTime = LocalDateTime.now().plusSeconds(oauth2Token.getExpires_in()).atZone(ZoneId.systemDefault()).toInstant();

            OAuth2AccessToken oAuth2AccessToken = new OAuth2AccessToken(
                    BEARER,
                    oauth2Token.getAccess_token(),
                    now,
                    expiredTime
            );


            return new OAuth2LoginAuthenticationToken(
                    clientRegistration,
                    loginToken.getAuthorizationExchange(),
                    principle,
                    null,
                    oAuth2AccessToken,
                    new OAuth2RefreshToken(
                            oauth2Token.getAccess_token(),
                            null,
                            oauth2Token.getRefreshTokenExpiresAt().toInstant()
                    )
            );
        }

        return null;

    }

    private Map<String,Object> getUserInfo(Oauth2Token oauth2Token, ClientRegistration clientRegistration) {

        String uri = clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri();

        MultiValueMap<String,String> multiValueMap = new HttpHeaders();

        multiValueMap.add("Authorization",oauth2Token.getToken_type()+" "+oauth2Token.getAccess_token());

        HttpEntity<String> requestEntity = new HttpEntity<>("",multiValueMap);

        ResponseEntity<String> exchange = restTemplate.exchange(
                uri,
                HttpMethod.GET,
                requestEntity,
                String.class
        );

        /*
         * 资源服务器 只是简单的返回了 用户的名称
         */
        try {
            return objectMapper.readValue(exchange.getBody(), new TypeReference<Map<String, Object>>() {
            });
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }


    }

    private Oauth2Token getOauth2Token(OAuth2LoginAuthenticationToken loginToken, ClientRegistration clientRegistration) {

        // 发起 POST 请求

        MultiValueMap<String,String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add(CLIENT_ID,clientRegistration.getClientId());
        requestBody.add(CLIENT_SECRET,clientRegistration.getClientSecret());

        HttpEntity<String> requestEntity = null;
        try {
            requestEntity = new HttpEntity<>(objectMapper.writeValueAsString(requestBody));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        String code = loginToken.getAuthorizationExchange().getAuthorizationResponse().getCode();

        URI uri = UriComponentsBuilder.fromHttpUrl(clientRegistration.getProviderDetails().getTokenUri())
                .queryParam(GRANT_TYPE, clientRegistration.getAuthorizationGrantType().getValue())
                .queryParam(OAuth2ParameterNames.CODE, code)

                .build().toUri();

        ResponseEntity<String> responseEntity = restTemplate.exchange(
                uri
                , HttpMethod.POST, requestEntity, String.class);


        String body = responseEntity.getBody();
        Oauth2Token oauth2Token = null;
        try {
            oauth2Token = objectMapper.readValue(body, Oauth2Token.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
        return oauth2Token;
    }
}
